
/*
 * 
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional informatio
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * 
 */
 
grammar LaSQLEasy;

options {
    language = Java;
}

@header {
    package cn.edu.thu.laud.lasql.parser;
    import java.util.Map;
   
}

@members {
    private List<String> recognitionErrors = new ArrayList<String>();
    
    public void displayRecognitionError(String[] tokenNames, RecognitionException e)
    {
        String hdr = getErrorHeader(e);
        String msg = getErrorMessage(e, tokenNames);
        recognitionErrors.add(hdr + " " + msg);
    }
    
    public List<String> getRecognitionErrors()
    {
        return recognitionErrors;
    }
    
    public void throwLastRecognitionError() throws Exception
    {
        if (recognitionErrors.size() > 0)
            throw new Exception(recognitionErrors.get((recognitionErrors.size()-1)));
    }

  
}

@lexer::header {
    package cn.edu.thu.laud.lasql.parser;
    
}

@lexer::members {
    List<Token> tokens = new ArrayList<Token>();
    
    public void emit(Token token) {
        state.token = token;
        tokens.add(token);
    }
    
    public Token nextToken() {
        super.nextToken();
        if (tokens.size() == 0)
            return Token.EOF_TOKEN;
        return tokens.remove(0);
    }
    
    private List<String> recognitionErrors = new ArrayList<String>();
    
    public void displayRecognitionError(String[] tokenNames, RecognitionException e)
    {
        String hdr = getErrorHeader(e);
        String msg = getErrorMessage(e, tokenNames);
        recognitionErrors.add(hdr + " " + msg);
    }
    
    public List<String> getRecognitionErrors()
    {
        return recognitionErrors;
    }
    
    public void throwLastRecognitionError() throws Exception
    {
        if (recognitionErrors.size() > 0)
            throw new Exception(recognitionErrors.get((recognitionErrors.size()-1)));
    }
}

query[List<LaSQLFunctionParser> maps]
    : selectStatement[maps] 
    | insertStatement[maps] 
    | updateStatement[maps] 
    | batchStatement[maps] 
       | deleteStatement[maps]
    ;


/**
 * SELECT
 *  (REVERSED)? <expression>
 * FROM
 *     <CF>
 * USING
 *     CONSISTENCY <LEVEL>
 * WHERE
 *     KEY = "key1" AND KEY = "key2" AND
 *     COL > 1 AND COL < 100
 * LIMIT <NUMBER>;
 */
selectStatement[List<LaSQLFunctionParser> maps]
    : 
      K_SELECT
          ( s1=selectExpression  
          | K_COUNT '(' s2=selectExpression ')' 
          )
          K_FROM ((IDENT | STRING_LITERAL | INTEGER) '.')? ( IDENT | STRING_LITERAL | INTEGER )
          ( K_USING K_CONSISTENCY K_LEVEL )?
          ( K_WHERE whereClause[maps] )?
          ( K_LIMIT INTEGER )?
          endStmnt
     
    ;

// [FIRST n] [REVERSED] name1[[[,name2],nameN],...]
// [FIRST n] [REVERSED] name1..nameN
selectExpression
    :
      ( K_FIRST INTEGER )?
      ( K_REVERSED  )?
      ( term
            (',' term )*
      | term RANGEOP term
      | '\*' 
      )
    ;

// relation [[AND relation] ...]
whereClause[List<LaSQLFunctionParser> maps]
    
    : laudRelation[maps] 
          (K_AND laudRelation[maps] )*
      | term
           K_IN '(' term
                  (',' term )* ')'
      
    ;

/**
 * INSERT INTO
 *    <CF>
 *    (KEY, <column>, <column>, ...)
 * VALUES
 *    (<key>, <value>, <value>, ...)
 * (USING
 *    CONSISTENCY <level>
 *   (AND TIMESTAMP <long>)?
 * )?;
 *
 * Consistency level is set to ONE by default
 */
insertStatement[List<LaSQLFunctionParser> maps] 
    :
      K_INSERT K_INTO ((IDENT | STRING_LITERAL | INTEGER) '.')? ( IDENT | STRING_LITERAL | INTEGER )
          '(' term ( ',' term  )+ ')'
        K_VALUES
          '(' term ( ',' laudTerm[maps] )+ ')'
        ( usingClause )?
      
    ;

usingClause
    : K_USING usingClauseObjective ( K_AND? usingClauseObjective )*
    ;

usingClauseDelete
    : K_USING usingClauseDeleteObjective ( K_AND? usingClauseDeleteObjective )*
    ;

usingClauseDeleteObjective
    : K_CONSISTENCY K_LEVEL 
    | K_TIMESTAMP ts=INTEGER 
    ;

usingClauseObjective
    : usingClauseDeleteObjective
    | K_TTL t=INTEGER  
    ;

/**
 * BEGIN BATCH [USING CONSISTENCY <LVL>]
 *   UPDATE <CF> SET name1 = value1 WHERE KEY = keyname1;
 *   UPDATE <CF> SET name2 = value2 WHERE KEY = keyname2;
 *   UPDATE <CF> SET name3 = value3 WHERE KEY = keyname3;
 *   ...
 * APPLY BATCH
 *
 * OR
 *
 * BEGIN BATCH [USING CONSISTENCY <LVL>]
 *   INSERT INTO <CF> (KEY, <name>) VALUES ('<key>', '<value>');
 *   INSERT INTO <CF> (KEY, <name>) VALUES ('<key>', '<value>');
 *   ...
 * APPLY BATCH
 *
 * OR
 *
 * BEGIN BATCH [USING CONSISTENCY <LVL>]
 *   DELETE name1, name2 FROM <CF> WHERE key = <key>
 *   DELETE name3, name4 FROM <CF> WHERE key = <key>
 *   ...
 * APPLY BATCH
 */
batchStatement[List<LaSQLFunctionParser> maps] 
    : 
      K_BEGIN K_BATCH ( usingClause )?
          batchStatementObjective[maps] ';'?  (batchStatementObjective[maps] ';'?  )*
      K_APPLY K_BATCH endStmnt
     
    ;

batchStatementObjective[List<LaSQLFunctionParser> maps] 
    : i=insertStatement[maps]  
    | u=updateStatement[maps] 
    | d=deleteStatement[maps] 
    ;

/**
 * UPDATE
 *     <CF>
 * (USING
 *     CONSISTENCY.ONE
 *    (AND TIMESTAMP <long>)?
 * )?
 * SET
 *     name1 = value1,
 *     name2 = value2
 * WHERE
 *     KEY = keyname;
 */
updateStatement[List<LaSQLFunctionParser> maps]
    :
      K_UPDATE ((IDENT | STRING_LITERAL | INTEGER) '.')? ( IDENT | STRING_LITERAL | INTEGER )
          ( usingClause )?
          K_SET laudTermPairWithOperation[maps] (',' laudTermPairWithOperation[maps])*
          K_WHERE ( term ('=' term 
                                    |
                                    K_IN '(' termList  ')' ))
      
    ;

/**
 * DELETE
 *     name1, name2
 * FROM
 *     <CF>
 * USING
 *     CONSISTENCY.<LVL>
 * WHERE
 *     KEY = keyname;
 */
deleteStatement[List<LaSQLFunctionParser> maps]
    :
      K_DELETE
          ( termList)?
          K_FROM ((IDENT | STRING_LITERAL | INTEGER) '.')? ( IDENT | STRING_LITERAL | INTEGER )
          ( usingClauseDelete )?
          ( K_WHERE term ('=' term
                                   | K_IN '(' termList  ')')
                  )?
    ;

term
    : ( K_KEY |STRING_LITERAL |INTEGER | UUID | IDENT |FLOAT) 
    ;
laudTerm[List<LaSQLFunctionParser> maps]
:
 (
 ( function {
 //System.out.println("one function");
 maps.add($function.laudFunction);}
 )
   |t=K_KEY   
   //{System.out.println("key"+$t.text);}
  | t=STRING_LITERAL  
   //{System.out.println("str"+$t.text);}
  |t= INTEGER  
   //{System.out.println("int"+$t.text);}
 | t=UUID  
 //{ System.out.println("uuid"+$t.text);}
  |t= IDENT  
  // {System.out.println("ident"+$t.text);}
 | t=FLOAT  
   //{System.out.println("float"+$t.text);}
 
 |(
// F U N C T I O N '(' STRING_LITERAL(','STRING_LITERAL)* ')' 
 //  R E M O T E
 remoteFunction
   {
 System.out.println("one function remote");}
 
  )
  )
	;

termList 
    : 
      term  (',' term)*
    ;

intTerm 
    : INTEGER 
    ;

laudTermPairWithOperation[List<LaSQLFunctionParser> maps]
    : term '=' (laudTerm[maps]
		               | term ( '+'  term 
                            | '-'? intTerm
                                   ))
    ;


laudRelation[List<LaSQLFunctionParser> maps]
    : term ('=' | '<' | '<=' | '>=' | '>') laudTerm[maps]
    ;

endStmnt
    : ';'?  EOF
    ;

function returns [LaSQLLocalFunctionParser laudFunction ]
:
	K_FUNCTION'(' class_name=STRING_LITERAL {$laudFunction=new LaSQLLocalFunctionParser($class_name.text);}
	(',' param=STRING_LITERAL {$laudFunction.add($param.text);})* ')' 
	;
remoteFunction returns [LaSQLFunctionParser laudFunction ]
:
	K_FUNCTION'(' STRING_LITERAL 
	(',' STRING_LITERAL )* ')' K_REMOTE {return null;}
	;

// Case-insensitive keywords
K_SELECT:      S E L E C T;
K_FROM:        F R O M;
K_WHERE:       W H E R E;
K_AND:         A N D;
K_KEY:         K E Y;
K_INSERT:      I N S E R T;
K_UPDATE:      U P D A T E;
K_WITH:        W I T H;
K_LIMIT:       L I M I T;
K_USING:       U S I N G;
K_CONSISTENCY: C O N S I S T E N C Y;
K_LEVEL:       ( O N E 
               | Q U O R U M 
               | A L L
               | A N Y
               | L O C A L '_' Q U O R U M
               | E A C H '_' Q U O R U M
               )
               ;
K_USE:         U S E;
K_FIRST:       F I R S T;
K_REVERSED:    R E V E R S E D;
K_COUNT:       C O U N T;
K_SET:         S E T;
K_BEGIN:       B E G I N;
K_APPLY:       A P P L Y;
K_BATCH:       B A T C H;
K_TRUNCATE:    T R U N C A T E;
K_DELETE:      D E L E T E;
K_IN:          I N;
K_CREATE:      C R E A T E;
K_KEYSPACE:    ( K E Y S P A C E
                 | S C H E M A );
K_COLUMNFAMILY:( C O L U M N F A M I L Y
                 | T A B L E );
K_INDEX:       I N D E X;
K_ON:          O N;
K_DROP:        D R O P;
K_PRIMARY:     P R I M A R Y;
K_INTO:        I N T O;
K_VALUES:      V A L U E S;
K_TIMESTAMP:   T I M E S T A M P;
K_TTL:         T T L;
K_ALTER:       A L T E R;
K_ADD:         A D D;
K_TYPE:        T Y P E;
K_FUNCTION:	F U N C T I O N;
K_REMOTE:	R E M O T E;
// Case-insensitive alpha characters
fragment A: ('a'|'A');
fragment B: ('b'|'B');
fragment C: ('c'|'C');
fragment D: ('d'|'D');
fragment E: ('e'|'E');
fragment F: ('f'|'F');
fragment G: ('g'|'G');
fragment H: ('h'|'H');
fragment I: ('i'|'I');
fragment J: ('j'|'J');
fragment K: ('k'|'K');
fragment L: ('l'|'L');
fragment M: ('m'|'M');
fragment N: ('n'|'N');
fragment O: ('o'|'O');
fragment P: ('p'|'P');
fragment Q: ('q'|'Q');
fragment R: ('r'|'R');
fragment S: ('s'|'S');
fragment T: ('t'|'T');
fragment U: ('u'|'U');
fragment V: ('v'|'V');
fragment W: ('w'|'W');
fragment X: ('x'|'X');
fragment Y: ('y'|'Y');
fragment Z: ('z'|'Z');
    
STRING_LITERAL
    : '\''
      { StringBuilder b = new StringBuilder(); }
      ( c=~('\'') { b.appendCodePoint(c);}
      | '\'' '\''            { b.appendCodePoint('\'');}
      )*
      '\''
      { setText(b.toString()); }
    ;

fragment DIGIT
    : '0'..'9'
    ;

fragment LETTER
    : ('A'..'Z' | 'a'..'z')
    ;
    
fragment HEX
    : ('A'..'F' | 'a'..'f' | '0'..'9')
    ;

RANGEOP
    : '..'
    ;

INTEGER
    : '-'? DIGIT+
    ;

/* Normally a lexer only emits one token at a time, but ours is tricked out
 * to support multiple (see @lexer::members near the top of the grammar).
 */
FLOAT
    : d=INTEGER r=RANGEOP
      {
          $d.setType(INTEGER);
          emit($d);
          $r.setType(RANGEOP);
          emit($r);
      }
      | INTEGER '.' INTEGER
    ;

IDENT
    : LETTER (LETTER | DIGIT | '_')*
    ;
    
COMPIDENT
    : IDENT ( ':' (IDENT | INTEGER))*
    ;
   
UUID
    : HEX HEX HEX HEX HEX HEX HEX HEX '-'
      HEX HEX HEX HEX '-'
      HEX HEX HEX HEX '-'
      HEX HEX HEX HEX '-'
      HEX HEX HEX HEX HEX HEX HEX HEX HEX HEX HEX HEX
    ;


WS
    : (' ' | '\t' | '\n' | '\r')+ { $channel = HIDDEN; }
    ;

COMMENT
    : ('--' | '//') .* ('\n'|'\r') { $channel = HIDDEN; }
    ;
    
MULTILINE_COMMENT
    : '/*' .* '*/' { $channel = HIDDEN; }
    ;

